package com.xci.sys.service;

import com.google.common.collect.Lists;
import com.xci.core.annotation.Log;
import com.xci.core.annotation.Valid;
import com.xci.core.base.BaseService;
import com.xci.core.base.PrimaryKeyValue;
import com.xci.core.base.RestMessage;
import com.xci.core.domain.HistoryInfo;
import com.xci.core.exceptions.AppException;
import com.xci.core.helper.CacheHelper;
import com.xci.core.helper.Helper;
import com.xci.core.internal.BatchUpdateCallback;
import com.xci.core.internal.Const;
import com.xci.core.web.TreeNode;
import com.xci.core.web.TreeNodeIndex;
import com.xci.sys.dao.ModuleDao;
import com.xci.sys.entity.SysModule;
import com.xci.sys.entity.SysUser;
import com.xci.sys.filter.ModuleFilter;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.cache.Cache;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 模块服务
 */
@Service
public class ModuleService extends BaseService {
    @Resource
    private ModuleDao moduleDao;//模块数据层对象
    @Resource
    private Cache userModuleCache;//用户权限模块缓存

    /**
     * 新建模块
     * @param entity 模块对象
     */
    @Log(module = Const.Module.Module, msg = "新建模块")
    @Transactional(rollbackFor = Exception.class)
    public RestMessage insert(@Valid SysModule entity) {
        return save(entity, true);
    }

    /**
     * 修改模块
     * @param entity 模块对象
     */
    @Log(module = Const.Module.Module, msg = "修改模块")
    @Transactional(rollbackFor = Exception.class)
    public RestMessage update(@Valid SysModule entity) {
        return save(entity, false);
    }

    /**
     * 批量保存模块对象
     * @param entities 模块对象集合
     */
    @Log(module = Const.Module.Module, msg = "批量保存模块对象")
    @Transactional(rollbackFor = Exception.class)
    public RestMessage batchSave(@Valid List<SysModule> entities) {
        for (SysModule module : entities) {
            if (Helper.isBlank(module.getId())) {                    //无主键,则新增
                save(module, true).ifFailException();
            } else if (!moduleDao.existById(module.getId())) {   //主键不存在,则新增
                save(module, true).ifFailException();
            } else {                                            //否则修改
                save(module, false).ifFailException();
            }
        }
        return RestMessage.success();
    }

    /**
     * 修改模块启用状态
     * @param ids    模块主键字符串
     * @param status 模块启用状态
     */
    @Log(module = Const.Module.Module, msg = "修改模块启用状态")
    @Transactional(rollbackFor = Exception.class)
    public RestMessage updateStatus(@NotBlank(message = "请指定模块字符串") String ids,
                                    @NotNull(message = "请指定模块启用状态") Boolean status) {
        String[] idList = Helper.splitToArray(ids);
        for (String id : idList) {
            moduleDao.updateStatus(id, status);
        }
        return RestMessage.success();
    }

    /**
     * 修改模块公共状态
     * @param ids          模块主键字符串
     * @param publicStatus 模块公共状态
     * @return 返回影响的行数
     */
    @Log(module = Const.Module.Module, msg = "修改模块公共状态")
    @Transactional(rollbackFor = Exception.class)
    public RestMessage updatePublicStatus(@NotBlank(message = "请指定模块主键") String ids,
                                          @NotNull(message = "请指定模块公共状态") Boolean publicStatus) {
        String[] idList = Helper.splitToArray(ids);
        for (String id : idList) {
            moduleDao.updatePublicStatus(id, publicStatus);
        }
        return RestMessage.success();
    }

    /**
     * 修改模块展开状态
     * @param ids          模块主键字符串
     * @param expandStatus 模块展开状态
     * @return 返回影响的行数
     */
    @Log(module = Const.Module.Module, msg = "修改模块展开状态")
    @Transactional(rollbackFor = Exception.class)
    public RestMessage updateExpandStatus(@NotBlank(message = "请指定模块主键字符串") String ids,
                                          @NotNull(message = "请指定模块展开状态") Boolean expandStatus) {
        String[] idList = Helper.splitToArray(ids);
        for (String id : idList) {
            moduleDao.updateExpandStatus(id, expandStatus);
        }
        return RestMessage.success();
    }

    /**
     * 修改模块预加载状态
     * @param ids              模块主键字符串
     * @param preloadingStatus 模块预加载状态
     * @return 返回影响的行数
     */
    @Log(module = Const.Module.Module, msg = "修改模块预加载状态")
    @Transactional(rollbackFor = Exception.class)
    public RestMessage updatePreloadingStatus(@NotBlank(message = "请指定模块主键字符串") String ids,
                                              @NotNull(message = "请指定模块预加载状态") Boolean preloadingStatus) {
        String[] idList = Helper.splitToArray(ids);
        for (String id : idList) {
            moduleDao.updatePreloadingStatus(id, preloadingStatus);
        }
        return RestMessage.success();
    }

    /**
     * 批量修改字段值
     * @param keyValues 字段值集合
     */
    @Log(module = Const.Module.Module, msg = "批量更新字段值")
    @Transactional(rollbackFor = Exception.class)
    public RestMessage batchUpdate(List<PrimaryKeyValue> keyValues) {
        return batchUpdateParent(keyValues, new BatchUpdateCallback() {
            @Override
            public void updateParentId(String id, String parentId) {
                moduleDao.updateParentId(id, parentId);
            }

            @Override
            public void updatePath(String id, String path) {
                moduleDao.updatePath(id, path);
            }
        });
    }

    /**
     * 删除模块
     * @param ids 机构主键字符串,多个逗号隔开
     */
    @Log(module = Const.Module.Module, msg = "删除模块")
    @Transactional(rollbackFor = Exception.class)
    public RestMessage delete(@NotBlank(message = "请指定模块主键多个逗号隔开") String ids) {
        String[] idList = Helper.splitToArray(ids);
        for (String id : idList) {
            SysModule old = moduleDao.selectById(id);
            if (old == null) throw new AppException(Helper.format("无效的模块主键:[{}]", id));
            moduleDao.delete(id);
            saveDeleteHistory(HistoryInfo.builder().pk(id).before(old).build());
        }
        return RestMessage.success();
    }

    /**
     * 检查模块主键是否存在
     * @param id 模块主键
     * @return 如果存在返回true, 否则返回false
     */
    public Boolean existById(@NotBlank(message = "请指定模块主键") String id) {
        return moduleDao.existById(id);
    }

    /**
     * 检查模块编码唯一性
     * @param code 模块编码
     * @return 如果存在返回true
     */
    public Boolean existByCode(@NotBlank(message = "请指定模块编码") String code) {
        return moduleDao.existByCode(code, null);
    }

    /**
     * 查询单个模块对象
     * @param id 模块主键
     * @return 返回模块对象
     */
    public SysModule selectById(@NotBlank(message = "请指定模块主键") String id) {
        return moduleDao.selectById(id);
    }

    /**
     * 根据编码查询单个模块对象
     * @param code 模块编码
     * @return 返回模块对象
     */
    public SysModule selectByCode(@NotBlank(message = "请指定模块编码") String code) {
        return moduleDao.selectByCode(code);
    }

    /**
     * 获取指定节点的直接子节点数
     * @param id 主键
     * @return 返回直接子节点数量
     */
    public Integer selectChildCount(@NotBlank(message = "请指定模块主键") String id) {
        return moduleDao.selectChildCount(id);
    }

    /**
     * 查询模块列表
     * @param filter 过滤条件对象
     * @return 返回模块列表
     */
    public List<SysModule> selectList(ModuleFilter filter) {
        return moduleDao.selectList(filter);
    }

    /**
     * 根据用户主键查询用户拥有的模块列表
     * @param user 用户对象
     * @return 返回模块列表
     */
    public List<SysModule> selectUserModuleList(SysUser user) {
        if (user == null) return Lists.newArrayList();
        if (user.getIsAdmin()) {
            var filter = new ModuleFilter();
            filter.setStatus(true);
            return moduleDao.selectList(filter);
        }
        return moduleDao.selectUserModuleList(user.getId());
    }

    /**
     * 根据用户主键查询用户拥有的模块列表
     * @param user 用户对象
     * @return 返回模块列表
     */
    public List<SysModule> getUserModuleCacheList(SysUser user) {
        if (user == null) return Lists.newArrayList();
        return CacheHelper.getCacheValue(userModuleCache, "list:" + user.getId(), () -> selectUserModuleList(user));
    }

    /**
     * 从缓存中根据用户查询模块键值对
     * @param user    用户对象
     * @param codeStr 编码字符串,多个编码逗号隔开,and关系
     * @return 返回模块Map
     */
    public boolean isAuthorize(SysUser user, String codeStr) {
        if (user == null || Helper.isBlank(codeStr)) return false;
        if (user.getIsAdmin()) return true;

        var codes = Helper.splitToArray(codeStr);
        Map<String, Object> moduleMap = CacheHelper.getCacheValue(userModuleCache, "map:" + user.getId(), () -> {
            List<SysModule> list = getUserModuleCacheList(user);
            Map<String, Object> map = new HashMap<>(list.size());
            for (SysModule entity : list) {
                map.put(entity.getCode(), 1);
            }
            return map;
        });
        for (String code : codes) {
            boolean has = moduleMap.containsKey(code);
            if (!has) {
                //任何一个为false,则直接返回false;
                return false;
            }
        }
        return true;
    }

    /**
     * 清除用户模块缓存
     * @param userId 用户主键
     */
    public void clearUserModuleCache(String userId) {
        userModuleCache.evict("list:" + userId);
        userModuleCache.evict("map:" + userId);
    }

    /**
     * 保存模块
     * @param entity  模块对象
     * @param created 是否新建
     */
    private RestMessage save(SysModule entity, boolean created) {
        //自动添加主键
        if (created && Helper.isBlank(entity.getId())) {
            entity.setId(Helper.nextIdStr());
        }
        if (Helper.isBlank(entity.getSpell())) {
            entity.setSpell(Helper.getSpell(entity.getName()));
        }
        if (entity.getParentId() == null) {//验证父节点
            entity.setParentId(Const.ROOT_NODE_ID);
        }

        if (entity.getParentId().equals(entity.getId())) {
            return RestMessage.fail("修改模块'" + entity.getName() + "'失败，上级模块不能是自己");
        }

        //模块编码唯一性验证
        if (moduleDao.existByCode(entity.getCode(), created ? null : entity.getId())) {
            String msg = Helper.format("模块编码 {} 已经存在", entity.getCode());
            return RestMessage.fail(msg);
        }

        if (created) {// 新建
            Integer childCount = moduleDao.selectChildCount(entity.getParentId());
            entity.setPath(childCount);
            moduleDao.insert(entity);
            saveInsertHistory(HistoryInfo.builder().pk(entity.getId()).after(moduleDao.selectById(entity.getId())).build());
        } else {     //修改
            var old = moduleDao.selectById(entity.getId());
            moduleDao.update(entity);
            saveUpdateHistory(HistoryInfo.builder().pk(entity.getId()).before(old).after(entity).build());
        }
        return RestMessage.success();
    }

    /**
     * 转为节点列表
     * @param models 模型列表
     */
    public List<TreeNode> convertToNodeList(List<SysModule> models) {
        List<TreeNode> nodes = new ArrayList<>();
        if (ObjectUtils.isEmpty(models)) {
            return nodes;
        }
        for (var item : models) {
            boolean hasChild = models.stream().anyMatch(p -> p.getParentId().equals(item.getId()));
            TreeNode node = new TreeNode();
            node.setId(item.getId());
            node.setPid(item.getParentId());
            node.setText(item.getName());
            node.setSpell(item.getSpell());
            node.setLeaf(hasChild ? 0 : 1);
            node.setIconCls(item.getWebCls());
            node.setUrl(item.getWebUrl());
            String state = "open";
            if (!item.getIsExpand() && hasChild) {
                state = "closed";
            }
            node.setState(state);
            nodes.add(node);
        }
        return nodes;
    }
}